#include "../include/kc.h"
#include "../include/video.h"

#define FLUSH_LINE 1	//mascara para flushear una linea
#define FLUSH_SCREEN 2	//mascara para flushear toda la pantalla
#define FLUSH_DEFAULT 0	//mascara para realizar el flush default

void videoBuffInit(videoBuff * vid);
void screen_putchar(videoBuff * vid, char c);
void screen_lineclr(videoBuff * vid);
void screen_enter(videoBuff * vid);
void screen_back(videoBuff * vid);
void screen_tab(videoBuff * vid);
void scroll(videoBuff * vid);
void pointer_scroll(videoBuff * vid);
void painted_scroll(videoBuff * vid);
int isBetween(int coord, int coord1, int coord2);
void buffer_flush_screen(videoBuff * vid);
void screen_pointer_show(videoBuff * vid);

void buffer_flush_screen(videoBuff * vid);

void buffer_line_flush_screen(videoBuff * vid);

void movecursor(videoBuff * vid, int pos);

char *video = (char *) 0xb8000;

/*posicion del puntero*/
int pointerX = 0;
int	pointerY = 0;

void
videoBuffInit(videoBuff * vid) {
	vid->pos=0;
	vid->posx=0;
	vid->posy=0;
	vid->set_go_up=0;

	/*posicion del puntero*/
	pointerX = 0;
	pointerY = 0;

	vid->paintedSection.colour = 0;
	vid->paintedSection.isPainted = 0;
	vid->paintedSection.xFin = 0;
	vid->paintedSection.xIni = 0;
	vid->paintedSection.yFin = 0;
	vid->paintedSection.yIni = 0;

	vid->scrolled=0;

	vid->cursorEnabled = 1;

	vid->flush = FLUSH_DEFAULT;

	vid->attr = WHITE_TXT;

	/*flag que avisa si vid->screenBeg (su utlima direccion) llego
	  o no al limite del buffer*/
	vid->bufferLimit=(NSCREENS==1);
	/*emulacion de la pantalla en el buffer*/
	vid->screenBeg=vid->videoBuffer;
	vid->vidBuffPos= vid->videoBuffer;
	vid->bufferFin = vid->videoBuffer+SCREEN_SIZE * 2 * NSCREENS;

	buffer_video_clear(vid);
}

void
movecursor(videoBuff * vid, int pos) {
	if (vid->cursorEnabled == 1) {
		outportb(0x3D4, 14);
		outportb(0x3D5, pos >> 8);
		outportb(0x3D4, 15);
		outportb(0x3D5, pos);
	}
}

void
show_hide_cursor(videoBuff * vid, int state) {
	if (state == 0) {
		outportb(0x3D4, 10);
		outportb(0x3D5, 32);
		vid->cursorEnabled = 0;
	}
	else {
		outportb(0x3D4, 10);
		outportb(0x3D5, 14);
		vid->cursorEnabled = 1;
	}
}

void
screen_manager(videoBuff * vid, char c) {

	switch(c){
		case '\n': screen_enter(vid);break;
		case '\r':screen_back(vid);break;
		case 3: screen_scroll_up(vid); break;
		case 4: screen_scroll_down(vid); break;
		default: screen_putchar(vid, c);  vid->posx+=1;

	}
}

/*permite hacer scroll hacia arriba de la pantalla por medio del buffer*/
void
screen_scroll_up(videoBuff * vid) {
	if(vid->screenBeg!=vid->videoBuffer) {
		vid->scrolled++;
		buffer_video_painted(vid);
		if(vid->scrolled==1) {
			/*se guarda donde originalmente se encontraba la pantalla*/
			vid->screenLast=vid->screenBeg;
			show_hide_cursor(vid, 0);
		}
		if(vid->screenBeg-vid->videoBuffer>=SCROLL_UP_DOWN) {
			/*scrolea la cantidad de filas dada por SCROLL_UP_DOWN*/
			vid->screenBeg-=SCROLL_UP_DOWN*MAX_COL*2;
		}
		else
			vid->screenBeg=vid->videoBuffer;
		vid->flush |= FLUSH_SCREEN;
	}
}

/*permite hacer scroll hacia abajo de la pantalla, si previamente se
  realizo un scroll hacia arriba*/
void
screen_scroll_down(videoBuff * vid) {
	if(vid->scrolled!=0) {
		buffer_video_painted(vid);
		vid->scrolled--;
		if(vid->scrolled==0)
			vid->screenBeg=vid->screenLast;
		else
			vid->screenBeg+=SCROLL_UP_DOWN*MAX_COL*2;
		vid->flush |= FLUSH_SCREEN;
	}
	if(vid->scrolled==0) {
		show_hide_cursor(vid, 1);
	}
}

/*pega en un buffer lo que se encuentre en vid->screenBeg, que emula a lo que
  hay en pantalla */
int
screen_get(videoBuff * vid, char * buff){

	memcpy(buff, vid->screenBeg, SCREEN_SIZE * 2);

	return 1;
}

/*esta funcion despinta el buffer si es que se lo pinto previamente*/
void
buffer_video_painted(videoBuff * vid)
{
	int i, j;
	if(vid->paintedSection.isPainted) {
		for (j = vid->paintedSection.yIni; j <= vid->paintedSection.yFin; j++) {
			for (i = vid->paintedSection.xIni; i <= vid->paintedSection.xFin; i++) {
				vid->screenBeg[(j * MAX_COL + i)*2+1] =
					INV_ATTR(vid->screenBeg[(j * MAX_COL + i)*2+1]);
			}
		}
		vid->flush |= FLUSH_SCREEN;
		vid->paintedSection.isPainted=0;
	}
}

/*pega en vid->screenBeg lo que se encuentre en el buffer*/
int
screen_set(videoBuff * vid, char * buff){

	memcpy(vid->screenBeg, buff, SCREEN_SIZE * 2);
	vid->flush |= FLUSH_SCREEN;

	return 1;
}

/*ingresa un enter en el buffer de video, el cual se encarga luego de
  pasarlo a video*/
void
screen_enter(videoBuff * vid){
	vid->posy+= 1;
	vid->posx=0;

	buffer_video_painted(vid);
	if(vid->scrolled!=0) /*esto se produce en el caso de que se ingrese un
	enter cuando se encuentra scroleada la pantalla*/
	{
		vid->screenBeg=vid->screenLast; /*vuelve a la pantalla original*/
		vid->scrolled=0;
		show_hide_cursor(vid, 1);
		vid->flush |= FLUSH_SCREEN;
	}
	if(vid->posy > MAX_ROW - 1) {
		vid->posy= MAX_ROW - 1;
		if(!vid->bufferLimit) {
			vid->screenBeg+=MAX_COL*2; /*si no esta en el limite, baja una fila
			la vid->screenBeg*/
			vid->vidBuffPos+=MAX_COL*2;
			painted_scroll(vid);
			if((vid->screenBeg+(SCREEN_SIZE * 2))==vid->bufferFin)
				vid->bufferLimit=1; /*se llega al limite del buffer*/

//			vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1] = BLACK_TXT;
		}
		else if(vid->vidBuffPos+MAX_COL*2==vid->bufferFin)
		{	scroll(vid);
//			vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1] = BLACK_TXT;
		}
		else
			vid->vidBuffPos+=MAX_COL*2;
		screen_lineclr(vid);
		vid->flush |= FLUSH_SCREEN;
	}
	else{
		vid->vidBuffPos+=MAX_COL*2;
		screen_lineclr(vid);
		vid->flush |= FLUSH_SCREEN;
	}
	vid->set_go_up=0;
	vid->pos=MAX_COL*vid->posy+vid->posx;
//	vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1] = WHITE_TXT;
}

/*borra el ultimo caracter del buffer de video(excepto la linea command >),
 el cual se encarga luego de pasarlo a video*/
void
screen_back(videoBuff * vid){
	buffer_video_painted(vid);
	if(vid->scrolled!=0)/*esto se produce en el caso de que se ingrese un
	enter cuando se encuentra scroleada la pantalla*/
	{
		vid->screenBeg=vid->screenLast; /*vuelve a la pantalla original*/
		vid->scrolled=0;
		show_hide_cursor(vid, 1);
		vid->flush |= FLUSH_SCREEN;
	}
	if(vid->posx > 9 || (vid->posx > 0 && vid->set_go_up > 0))
	{	vid->posx-=1;
		screen_putchar(vid, ' ');
		vid->flush |= FLUSH_LINE;
	}
	else if(vid->posx == 0){
		vid->posx= MAX_COL - 1;
		vid->posy-=1;
		vid->set_go_up-=1;
		vid->vidBuffPos-=MAX_COL*2;
		screen_putchar(vid, ' ');
	}

	vid->pos=MAX_COL*vid->posy+vid->posx;
}
/*ingresa un caracter en el buffer de video, el cual se encarga luego de
  pasarlo a video*/
void
screen_putchar(videoBuff * vid, char c){

	buffer_video_painted(vid);
	if(vid->scrolled!=0)/*esto se produce en el caso de que se ingrese un
	enter cuando se encuentra scroleada la pantalla*/
	{
		vid->screenBeg=vid->screenLast; /*vuelve a la pantalla original*/
		vid->scrolled=0;
		show_hide_cursor(vid, 1);
		vid->flush |= FLUSH_SCREEN;
	}
	if(vid->posx == MAX_COL)
	{	vid->posx = 0;
		vid->posy+=1;
		vid->set_go_up+=1;
		vid->vidBuffPos+=MAX_COL*2;
		vid->flush |= FLUSH_SCREEN;
	}

	if(vid->posy > MAX_ROW - 1)
		vid->posy = MAX_ROW - 1;

	vid->pos=2*MAX_COL*vid->posy+2*vid->posx;

	vid->screenBeg[vid->pos] = c;
	vid->screenBeg[vid->pos+1] = vid->attr;
	vid->flush |= FLUSH_LINE;
	if(vid->posx==MAX_COL-1 && vid->posy==MAX_ROW-1)
	{
		if(!vid->bufferLimit) {
			vid->screenBeg+=MAX_COL*2;
			painted_scroll(vid);
			vid->vidBuffPos+=MAX_COL*2;
			if((vid->screenBeg+(SCREEN_SIZE * 2))==vid->bufferFin)
				vid->bufferLimit=1;
		}
		else if((vid->vidBuffPos+MAX_COL*2)==vid->bufferFin)
			scroll(vid);
		else
			vid->vidBuffPos+=MAX_COL*2;
		screen_lineclr(vid);
		vid->flush |= FLUSH_SCREEN;
		vid->pos = (2*MAX_COL*vid->posy)/2;

		vid->posx=-1;
		vid->set_go_up+=1;
	}
	else
		vid->pos = vid->pos/2 + 1;
}

/*hace un scroll del buffer de video*/
void
scroll(videoBuff * vid){
	memmove(vid->videoBuffer, vid->videoBuffer+2*MAX_COL,(SCREEN_SIZE * 2 * NSCREENS)-(2*MAX_COL));
	painted_scroll(vid); //actualizo la zona seleccionada
}

//actualizo el puntero despues del scroll
void
pointer_scroll(videoBuff * vid) {
	screen_pointer_move(vid, &pointerX, &pointerY);
}

//actualizo la zona pintada despues del scroll
void
painted_scroll(videoBuff * vid) {
	if (vid->paintedSection.isPainted) {
		vid->paintedSection.yIni-=1;
		vid->paintedSection.yFin-=1;
		if ( (vid->paintedSection.yIni == vid->paintedSection.yFin)
				|| !isBetween(vid->paintedSection.yIni, 0, MAX_ROW-1)
				|| !isBetween(vid->paintedSection.yFin, 0, MAX_ROW-1) )
		{
			vid->paintedSection.isPainted = 0;
			return;
		}
	}
}
/*se limpia lo que hay en video*/
void
screen_clear(videoBuff * vid) {
	unsigned int i = 0;

	while(i < (SCREEN_SIZE*2))
	{
		video[i]= ' ';
		i++;
		video[i]= WHITE_TXT;
		i++;
	};

	//limpiar el buffer del mouse, desseleccionar lo pintado.
}

/*limpia el buffer de video*/
void
buffer_video_clear(videoBuff * vid) {
	unsigned int i = 0;

	while(i<(SCREEN_SIZE * 2 * NSCREENS))
	{
		vid->screenBeg[i]=' ';
		i++;
		vid->screenBeg[i]= WHITE_TXT;
		i++;
	}
}

/*limpia una linea en el buffer de video*/
void
screen_lineclr(videoBuff * vid){

	int x,y;
	int poslocal;

	if(vid->posy > MAX_ROW - 1)
		y = MAX_ROW - 1;
	else
		y = vid->posy;

	for(x=0;x<MAX_COL;x++)
	{	poslocal=2*MAX_COL*y;
		vid->screenBeg[poslocal+2*x]= ' ';
		if(!(vid->paintedSection.isPainted && y>=vid->paintedSection.yIni && y<=vid->paintedSection.yFin))
				vid->screenBeg[poslocal+2*x+1]= WHITE_TXT;
	}
}

/*para escribir mas de un caracter*/
int
screen_write(videoBuff * vid, char * buffer, int cant){

	int i;

	for(i=0;i<cant;i++){
		screen_manager(vid, buffer[i]);
	}

	return cant;
}

/*remueve el puntero del mouse*/
void
screen_pointer_remove(videoBuff * vid) {
	video[(pointerY*MAX_COL + pointerX)*2+1] =
		vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1];
}

/*ingresa el puntero del mouse*/
void
screen_pointer_show(videoBuff * vid) {
	video[(pointerY*MAX_COL + pointerX)*2+1] =
		INV_ATTR(vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1]);
}

void
screen_pointer_move(videoBuff * vid, int * x, int * y) {
	//borro el puntero anterior
	video[(pointerY*MAX_COL + pointerX)*2+1] =
		INV_ATTR(video[(pointerY*MAX_COL + pointerX)*2+1]);

	if ( *x < 0 )
		*x = 0;

	if ( *x > MAX_COL - 1 )
		*x = MAX_COL - 1;

	if ( *y < 0 )
		*y = 0;

	if ( *y > MAX_ROW - 1 )
		*y = MAX_ROW - 1;

	pointerX = *x;
	pointerY = *y;

	//seteo el puntero actualizado
	video[(pointerY*MAX_COL + pointerX)*2+1] =
		INV_ATTR(vid->screenBeg[(pointerY*MAX_COL + pointerX)*2+1]);
}

/*limpia lo pintado por el mouse*/
void
screen_clean_painted(videoBuff * vid) {
	int i, j;
	char color;
	for (i = vid->paintedSection.xIni; i <= vid->paintedSection.xFin; i++) {
		for (j = vid->paintedSection.yIni; j <= vid->paintedSection.yFin; j++) {
			color = INV_ATTR(vid->screenBeg[(j * MAX_COL + i)*2+1]);
			vid->screenBeg[(j * MAX_COL + i)*2+1] = color;
			video[(j * MAX_COL + i)*2+1] = color;
		}
	}
}

/*pinta lo que indica el mouse*/
void
screen_paint(videoBuff * vid, int xIni, int yIni, int xFin, int yFin) {
	int i, j;

	if ( (xIni == xFin && yIni == yFin) || !isBetween(xIni, 0, MAX_COL-1)
		|| !isBetween(xFin, 0, MAX_COL-1) || !isBetween(yIni, 0, MAX_ROW-1)
		|| !isBetween(yFin, 0, MAX_ROW-1) )
	{
		//pinto la zona anterior del color de fondo default
		if (vid->paintedSection.isPainted != 0)
			screen_clean_painted(vid);
		vid->paintedSection.isPainted = 0;
		return;
	}

	//este if me dice si no cambiaron las coordenadas. eso me evita repintar todo
	if (vid->paintedSection.xIni == ((xIni <= xFin) ? xIni : xFin) &&
	vid->paintedSection.yIni == ((yIni <= yFin) ? yIni : yFin) &&
	vid->paintedSection.xFin == ((xIni <= xFin) ? xFin : xIni) &&
	vid->paintedSection.yFin == ((yIni <= yFin) ? yFin : yIni) )
		return;

	//pinto la zona anterior del color de fondo default
	if (vid->paintedSection.isPainted != 0)
		screen_clean_painted(vid);

	vid->paintedSection.xIni = (xIni <= xFin) ? xIni : xFin;
	vid->paintedSection.yIni = (yIni <= yFin) ? yIni : yFin;
	vid->paintedSection.xFin = (xIni <= xFin) ? xFin : xIni;
	vid->paintedSection.yFin = (yIni <= yFin) ? yFin : yIni;
	vid->paintedSection.isPainted = 1;

	char color;
	//y pinto la zona especial del color correspondiente
	for (i = vid->paintedSection.xIni; i <= vid->paintedSection.xFin; i++) {
		for (j = vid->paintedSection.yIni; j <= vid->paintedSection.yFin; j++) {
			color = INV_ATTR(vid->screenBeg[(j * MAX_COL + i)*2+1]);
			vid->screenBeg[(j * MAX_COL + i)*2+1] = color;
			video[(j * MAX_COL + i)*2+1] = color;
		}
	}
}

/*en el caso de que el cursor este dentro de la zona pintada*/
int
is_inside_painted(videoBuff * vid, int x, int y) {
	if ( !vid->paintedSection.isPainted )
		return 0;	//si no hay nada pintado, devuelvo 0

	if ( isBetween(x, vid->paintedSection.xIni, vid->paintedSection.xFin)
	  && isBetween(y, vid->paintedSection.yIni, vid->paintedSection.yFin))
		return 1;

	return 0;
}

/*levanta la zona pintada, y pinta en el buffer esa zona hasta que se
 realice una accion */
int
screen_get_painted(videoBuff * vid, char * buffer) {
	int cont = 0, i, j;

	if ( !vid->paintedSection.isPainted )
		return 0;	//si no hay nada pintado, devuelvo 0

	for (j = vid->paintedSection.yIni; j <= vid->paintedSection.yFin; j++) {
		for (i = vid->paintedSection.xIni; i <= vid->paintedSection.xFin; i++) {
			buffer[cont++] = vid->screenBeg[(j * MAX_COL + i)*2];
		}
	}
	vid->paintedSection.isPainted=1;

	return cont;
}

/*levanta la parte pintada de solo una linea (no le pone el \n al final*/
int
screen_get_painted_line(videoBuff * vid, char * buffer, int y) {
	int cont = 0, i;

	if ( !vid->paintedSection.isPainted ||
		!isBetween(y, vid->paintedSection.yIni, vid->paintedSection.yFin) ) {
		return 0;	//si no hay nada pintado o la fila es erronea, devuelvo 0
	}

	for (i = vid->paintedSection.xIni; i <= vid->paintedSection.xFin; i++) {
		buffer[cont++] = vid->screenBeg[(y * MAX_COL + i)*2];
	}
	vid->paintedSection.isPainted=1;

	return cont;
}

/*devuelve las cordenadas pintadas*/
paintedSectionT
screen_get_selected_coords(videoBuff * vid) {
	return vid->paintedSection;
}


int
isBetween(int coord, int coord1, int coord2) {

	if (coord1 > coord2) {
		int aux = coord1;
		coord1 = coord2;
		coord2 = aux;
	}

	if (coord >= coord1 && coord <= coord2)
		return 1;

	return 0;
}

void
screen_set_pos(videoBuff * vid, int x, int y){
	vid->posx = x;
	vid->posy = y;
	vid->pos = (MAX_COL*vid->posy+vid->posx);

	vid->vidBuffPos = vid->screenBeg + vid->pos*2;

	return;
}

void
screen_get_pos(videoBuff * vid, int * x, int * y){
	*x = vid->posx;
	*y = vid->posy;

	return;
}

/*manda lo que hay en la pantalla emulada en el buffer de video, al video*/
void
buffer_flush_screen(videoBuff * vid){
	memmove(video, vid->screenBeg, SCREEN_SIZE * 2);
}

/*manda solo lo que apunta vid->vidBuffPos(una linea del buffer de video) a la
 video */
void
buffer_line_flush_screen(videoBuff * vid){
	memmove(video+vid->posy*MAX_COL*2, vid->vidBuffPos/*vid->screenBeg+vid->posy*MAX_COL*2*/, MAX_COL * 2);
}

void
flush_screen(videoBuff * vid){
	char flushMask = (char)vid->flush;

	if ( (flushMask & FLUSH_SCREEN) || flushMask == FLUSH_DEFAULT) {
		buffer_flush_screen(vid);
		show_hide_cursor(vid, vid->cursorEnabled);
	}
	else if (flushMask & FLUSH_LINE) {
		buffer_line_flush_screen(vid);
	}

	//muestro el puntero del mouse
	screen_pointer_show(vid);

	movecursor(vid, vid->pos);

	vid->flush = FLUSH_DEFAULT;
}

void
scrollFullScreen(videoBuff * vid) {
	int i;
	for (i = 0; i < MAX_ROW; i++)
		screen_enter(vid);
}

void
changeCurAttr(videoBuff * vid, char attr) {
	vid->attr = attr;
}



